package org.hiberlyzer.impl;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

import org.hiberlyzer.DataColumn;
import org.hiberlyzer.DataSet;
import org.hibernate.EntityMode;
import org.hibernate.Hibernate;
import org.hibernate.HibernateException;
import org.hibernate.Session;
import org.hibernate.engine.SessionFactoryImplementor;
import org.hibernate.metadata.ClassMetadata;
import org.hibernate.metadata.CollectionMetadata;
import org.hibernate.type.CollectionType;
import org.hibernate.type.Type;

/**
 * A DataSet containing results that are Hibernate-mapped entities. Each mapped
 * property of the mapped class will be treated as a column.
 */
public class EntityDataSet implements DataSet
{
	private List _results;
	private Session _session;
	private ClassMetadata classMetadata;
	private List<DataColumn> properties;

	protected EntityDataSet(List list, Session session)
	{
		this._results = list;
		this._session = session;
		determineColumns();
	}

	public List<DataColumn> getColumns()
	{
		return Collections.unmodifiableList(properties);
	}

	public int getResultCount()
	{
		return _results.size();
	}

	private void determineColumns()
	{
		properties = new ArrayList<DataColumn>();
		for(Object object : _results)
		{
			if(object != null)
			{
				try
				{
					classMetadata = _session.getSessionFactory().getClassMetadata(object.getClass());
					for(String propertyName : classMetadata.getPropertyNames())
					{
						Type propertyType = classMetadata.getPropertyType(propertyName);
						Class propertyClass = propertyType.getReturnedClass();
			
						if(propertyType.isEntityType())
						{
							ClassMetadata propertyMetadata = _session.getSessionFactory().getClassMetadata(propertyClass);
							properties.add(new EntityDataColumn(propertyName, propertyMetadata, _session.getEntityMode()));
						}
						else if(propertyType.isCollectionType())
						{
							Type elementType = ((CollectionType) propertyType).getElementType(((SessionFactoryImplementor)_session.getSessionFactory()));
							Class elementClass = elementType.getReturnedClass();
							CollectionMetadata collectionMetadata = _session.getSessionFactory().getCollectionMetadata(((CollectionType) propertyType).getRole());
							properties.add(new CollectionDataColumn(propertyName, propertyClass, elementClass, collectionMetadata));
						}
						else
						{
							properties.add(new SimpleDataColumn(propertyName, propertyClass));
						}
					}
					Collections.sort(properties, new Comparator<DataColumn>(){
						public int compare(DataColumn dc1, DataColumn dc2)
						{
							return dc1.getName().compareToIgnoreCase(dc2.getName());
						}});
					String identityName = classMetadata.getIdentifierPropertyName();
					Class identityClass = classMetadata.getIdentifierType().getReturnedClass();
					properties.add(0, new SimpleDataColumn(identityName, identityClass));
					return;
				}
				catch (HibernateException e)
				{
					e.printStackTrace();
					return;
				}
			}
		}
	}

	public Object getValue(int rowIndex, int columnIndex)
	{
		Object rowObject = _results.get(rowIndex);
		DataColumn column = properties.get(columnIndex);
		EntityMode entityMode = _session.getEntityMode();
		
		try
		{
			String columnName = column.getName();
			if(classMetadata.getIdentifierPropertyName().equals(columnName))
			{
				return classMetadata.getIdentifier(rowObject, entityMode);
			}
			
			// actual value for the property
			Object value = classMetadata.getPropertyValue(rowObject, columnName, entityMode);
			
			Type propertyType = classMetadata.getPropertyType(columnName);
			// merge will force replacement of proxy values with initialized implementations
			if(propertyType.isEntityType())
			{
				value = _session.merge(value);
			}
			else if(propertyType.isCollectionType())
			{
				Hibernate.initialize(value);
			}
			return value;
		}
		catch(Exception ex)
		{
			ex.printStackTrace();
			return "???";
		}
	}
}
